// Copyright (c) 2009 - Quildreen <quildreen@gmail.com>                     
//                                                                          
//  Permission is hereby granted, free of charge, to any person              
//  obtaining a copy of this software and associated documentation files     
//  (the "Software"), to deal in the Software without restriction,           
//  including without limitation the rights to use, copy, modify, merge,     
//  publish, distribute, sublicense, and/or sell copies of the Software,     
//  and to permit persons to whom the Software is furnished to do so,        
//  subject to the following conditions:                                     
//                                                                           
//  The above copyright notice and this permission notice shall be           
//  included in all copies or substantial portions of the Software.          
//                                                                           
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,          
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF       
//  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                    
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE   
//  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION   
//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION    
//  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 

 
/**
 * lily.sm.core
 * 
 * Implements screen management through state machines
 * 
 * Requires: `time` and `input.core` 
 **/
lily.require("time", "input.core");
lily.onLoad(function() {

    lily.defineModule("sm.core");


    /**
     * class lily.sm.GameScreen
     * 
     * Implements a basic game screen.
     **/
    lily.sm.GameScreen = Class.create(
    {
        /**
         * new lily.sm.GameScreen()
         **/
        initialize: function()
        {
            this.alive = true;
        },
    
    
        /**
         * lily.sm.GameScreen#update(lag) -> undefined
         * - lag (Number): the amount of unxexpected delay between the old
         * frame and the current one, in miliseconds.
         * 
         * Updates the screen. This will be called at every frame by the
         * Application object and child classes are expected to rewrite this
         * function as they see fit.
         *
         **/
        update: function(lag){
            /* void */
        },
    
    
        /**
         * lily.sm.GameScreen#enter() -> undefined
         * 
         * Enters the screen. This will be called by the Application object
         * once we're entering this screen.
         **/
        enter: function(){
            /* void */
        },
    
    
        /**
         * lily.sm.GameScreen#exit() -> undefined
         * 
         * Exits the screen. This will be called by the Application object once
         * we're exiting this screen.
         **/
        exit: function(){
            /* void */
        }
    });


    /**
     * class lily.sm.Transition < lily.sm.GameScreen
     * 
     * Implements a basic transition.
     **/
    lily.sm.Transition = Class.create(lily.sm.GameScreen,
    {
        /**
         * new lily.sm.Transition()
         **/
        initialize: function($super)
        {
            $super();
        }
    });


    /**
     * class lily.sm.State
     * 
     * Implements a state machine's state. A state is constitued of a screen
     * and a transition instance, and is considered alive as long as either one
     * of those instances are alive.
     **/
    lily.sm.State = Class.create(
    {
        /**
         * new lily.sm.State(screen[, transition])
         * - screen ([[lily.sm.GameScreen]]): associated GameScreen instance.
         * - transition ([[lily.sm.Transition]]): optional transition to use
         * when entering this state.
         **/
        initialize: function(screen, transition)
        {
            this.screen = screen;
            this.transition = transition;
        },
    
    
        /**
         * lily.sm.State#update(lag) -> undefined
         * - lag (Number) the amount of unexpected delay between the last frame
         * and the current one.
         * 
         * Updates the state, and calls `update` in both the
         * [[lily.sm.GameScreen]] and [[lily.sm.Transition]] instances that are
         * managed by this state.
         **/
        update: function(lag)
        {
            if (this.screen && this.screen.alive) {
                this.screen.update(lag);
            }
        
            if (this.transition && this.transition.alive) {
                this.transition.update(lag);
            }
        },
    
    
        /**
         * lily.sm.State#enter() -> undefined
         * 
         * Notifies we're entering this state and calls `enter` in both the
         * [[lily.sm.GameScreen]] and [[lily.sm.Transition]] instances that are
         * managed by this state.
         **/
        enter: function()
        {
            if (this.screen) {
                this.screen.enter();
            }
            
            if (this.transition) {
                this.transition.enter();
            }
        },  
        
        
        /**
         * lily.sm.State#exit() -> undefined
         * 
         * Notifies we're exiting this state and calls `exit` in both the
         * [[lily.sm.GameScreen]] and [[lily.sm.Transition]] instances that are
         * managed by this state.
         **/
        exit: function()
        {
            if (this.screen) {
                this.screen.exit();
            }
            
            if (this.transition) {
                this.transition.exit();
            }
        },
        
        
        /**
         * lily.sm.State#isAlive() -> Boolean
         * 
         * Returns if the current state is alive. A state is considered alive
         * if either the transition or the screen it manages are alive.
         **/
        isAlive: function()
        {
            return (this.screen && this.screen.alive)
                   || (this.transition && this.transition.alive);
        }
    });


    /**
     * lily.sm.Application
     * 
     * Manages the application through a state machine-based approach.
     **/
    lily.defineNamespace("lily.sm.Application");
    Object.extend(lily.sm.Application, (function() {

        var Application = lily.sm.Application;
    
        /**
         * lily.sm.Application.initialize(canvas[, options]) -> undefined
         * - canvas (HTMLElement) the element to render the screens to.
         **/
        function initialize(canvas)
        {
            var options = Object.extend(
            {
                active: false,
                default_transition: null
            }, arguments[2] || Application);
        
            Application.display = new lily.graphics.Display();
            Application.parent = $(canvas);
            lily.input.Manager.window = Application.parent;
        
            if (!Application.parent){
                 throw new Error("Application requires a HTMLElement for the "
                                 + "rendering.");
            }
        
            Application.current_state = null;
            Application.old_state = null;
            Application.state_pool = [];
        
            Application.default_transition = options.default_transition;
            Application.active = options.active;
            Application.clock = new lily.time.Clock(Application.update);
        }
        
    
        /**
         * lily.sm.Application._setState(new_state) -> undefined
         * - new_date ([[lily.sm.State]]): the state to change to.
         * 
         * Internally called to set the current state.
         **/
        function _setState(new_state)
        {
            if (Application.current_state) {
                Application.current_state.exit();
            }
        
            Application.old_state = Application.current_state;
            Application.current_state = new_state;
        
            if (Application.current_state) {
                Application.current_state.enter();
            }
        }
    
    
        /**
         * lily.sm.Application.changeState(new_screen[, in_transition][, out_transition][, force = false]) -> undefined
         * - new_screen ([[lily.sm.GameScreen]]): the screen to change to.
         * - transition ([[lily.sm.Transition]]): the transition to apply when
         * entering the new screen. Defaults to
         * [[lily.sm.Application.default_transition]].
         * - out_transition ([[lily.sm.Transition]]): the transition to apply
         * when exiting the current screen. Defaults to
         * [[lily.sm.Application.default_transition]].  
         * - force (Boolean): If true, we'll change the screen imediately
         * instead of putting it on the queue first.
         * 
         * Changes the current state to a new one. You may specify optional in
         * and out transitions and the function will generate the appropriate
         * State objects for you.
         **/
        function changeState(new_screen, in_transition, out_transition, force)
    {
            var old_screen = (Application.old_state) ?
                    Application.old_state.screen : null;
            var out_state = new lily.sm.State(old_screen, out_transition);
            var in_state = new lily.sm.State(new_screen, in_transition);
        
            if (force) {
                Application.state_pool.splice(0, 0, in_state);
                _setState(out_state);
            } else {
                Application.state_pool.push(out_state);
                Application.state_pool.push(in_state);
            }
        }
    
    
        /**
         * lily.sm.Application.nextState() -> undefined
         * 
         * Changes to the next state in the queue.
         **/
        function nextState()
        {
            _setState(Application.state_pool.shift());
        }
    
    
        /**
         * lily.sm.Application.prevState() -> undefined
         * 
         * Changes to the previous state in the queue.
         **/
        function prevState()
        {
            _setState(Application.old_state);
        }
    
    
        /**
         * lily.sm.Application.update(lag) -> undefined
         * - lag (Number) the amount of unexpected delay from the previous
         * frame to the current one.
         * 
         * Updates the current state. This calls update in the current state and
         * tests if it died, so we can move to the next state in the queue.
         **/
        function update(lag)
        {
            var current_state = Application.current_state,
                input = lily.input.Manager;

            input.update();
        
            if (Application.active) {
                if (current_state) {
                    current_state.update(lag);
                }
                if (!current_state || !current_state.isAlive()) {
                    if (Application.state_pool.length > 0) {
                        Application.nextState();
                    } else {
                        Application.clock.stop();
                    }
                }
            }
        
            input.wheel_delta = 0;
        }
    
    
        /**
         * lily.sm.Application.run() -> undefined
         * 
         * Runs the state machine application.
         **/
        function run()
        {
            Application.active = true;
            Application.clock.start();
        }
    
    
        return {
            state_pool: [],
            default_transition: null,
            active: true,
            initialize: initialize,
            changeState: changeState,
            nextState: nextState,
            prevState: prevState,
            update: update,
            run: run
        };
    }()));
}); // onLoad